module exe_stage(
    input                          clk           ,
    input                          reset         ,
    //allowin
    input                          ms_allowin    ,
    output                         es_allowin    ,
    //from ds
    input                          ds_to_es_valid,
    input  [154:0] ds_to_es_bus  ,
    //to ms
    output                         es_to_ms_valid,
    output [70:0] es_to_ms_bus  ,
    //to id
    output [38:0] es_to_id_bus  ,
    // data sram interface
    output                         data_sram_en  ,
    output [ 3:0]                  data_sram_we ,
    output [31:0]                  data_sram_addr,
    output [31:0]                  data_sram_wdata 
);

wire es_mul_w;
wire es_mulh_w;
wire es_mulh_wu;
wire es_div_w;
wire es_div_wu;
wire es_mod_w;
wire es_mod_wu;

wire [63:0] unsigned_prod, signed_prod;

wire signed_output_valid;
wire unsigned_output_valid;

wire signed_input_valid;
wire signed_s_axis_divisor_tready;
wire signed_s_axis_dividend_tready;
wire [63:0] signed_output_data;
reg [1:0] signed_status;
reg [1:0] signed_next_status;

wire unsigned_input_valid;
wire unsigned_s_axis_divisor_tready;
wire unsigned_s_axis_dividend_tready;
wire [63:0] unsigned_output_data;
reg [1:0] unsigned_status;
reg [1:0] unsigned_next_status;

reg         es_valid      ;
wire        es_ready_go   ;

wire [11:0] es_alu_op ;
wire es_res_from_mem;
wire         es_gr_we;
wire         es_mem_we;
wire  [ 4:0] es_dest;
wire  [31:0] es_rj_value;
wire  [31:0] es_rkd_value;
wire  [31:0] es_pc;
wire  [31:0] es_alu_src1;
wire  [31:0] es_alu_src2;
wire es_rf_we;
wire [31:0] es_alu_result;
wire [31:0] alu_result;

reg [154:0] ds_to_es_bus_reg;

always@(posedge clk) begin
    if (ds_to_es_valid && es_allowin) begin
        ds_to_es_bus_reg <= ds_to_es_bus;
    end
end

assign  {
            es_alu_op  ,  //154:143
            es_res_from_mem    ,  //142
            es_gr_we    ,  //141
            es_mem_we   ,  //140
            es_dest   ,  //139:135
			es_rkd_value      ,  //134:103
            es_pc     ,  //102:71
            es_alu_src1  ,  //70:39
            es_alu_src2 ,  //38:7
            es_mul_w       ,  //6
            es_mulh_w      ,  //5
            es_mulh_wu        ,  //4
            es_div_w      ,  //3
            es_div_wu    ,  //2
            es_mod_w   ,  //1
            es_mod_wu          //0
} = ds_to_es_bus_reg;

assign es_to_ms_bus = {
                        es_res_from_mem,//70
                        es_alu_result,//69:38
                        es_gr_we,//37
                        es_dest,//36:32
                        es_pc//31:0
};

assign es_to_id_bus = {
                        es_res_from_mem,//38
                        es_alu_result,//37:6
                        es_rf_we,//5
                        es_dest//4:0

};

assign es_rf_we = es_gr_we & es_valid;
assign es_ready_go = (~(es_div_w | es_mod_w | es_div_wu | es_mod_wu)) | ((es_div_w | es_mod_w) & signed_output_valid) | ((es_div_wu | es_mod_wu) & unsigned_output_valid);
assign es_allowin = (~es_valid)|(es_ready_go & ms_allowin);
assign es_to_ms_valid = es_valid & es_ready_go;

always @(posedge clk) begin
    if (reset) begin     
        es_valid <= 1'b0;
    end
    else if (es_allowin) begin 
        es_valid <= ds_to_es_valid;
    end
end


alu u_alu(
    .alu_op     (es_alu_op    ),
    .alu_src1   (es_alu_src1  ),
    .alu_src2   (es_alu_src2  ),
    .alu_result (alu_result)
    );

assign unsigned_prod = es_alu_src1 * es_alu_src2;
assign signed_prod = $signed(es_alu_src1) * $signed(es_alu_src2);

always @(posedge clk) begin
    if (reset) begin     
        signed_status <= 2'd0;
    end
    else begin 
        signed_status <= signed_next_status;
    end
end

always @(*) begin
	case(signed_status)
		2'd0://waiting inst
			signed_next_status = (es_div_w | es_mod_w) & es_valid ? 2'd1 : 2'd0;
		2'd1://waiting input
			signed_next_status = signed_s_axis_divisor_tready & signed_s_axis_dividend_tready ? 2'd2 : 2'd1;
		2'd2://waiting output
			signed_next_status = signed_output_valid ? 2'd0 : 2'd2;
		default:
			signed_next_status = 2'd3;
	endcase
end
	
assign signed_input_valid = signed_status == 2'd1;

mydiv u_mydiv(
	.aclk (clk),
	.s_axis_divisor_tvalid (signed_input_valid),
	.s_axis_divisor_tready (signed_s_axis_divisor_tready),
	.s_axis_divisor_tdata (es_alu_src2),
	.s_axis_dividend_tvalid (signed_input_valid),
	.s_axis_dividend_tready (signed_s_axis_dividend_tready),
	.s_axis_dividend_tdata (es_alu_src1),
	.m_axis_dout_tvalid (signed_output_valid),
	.m_axis_dout_tdata (signed_output_data)
	);

always @(posedge clk) begin
    if (reset) begin     
        unsigned_status <= 2'd0;
    end
    else begin 
        unsigned_status <= unsigned_next_status;
    end
end

always @(*) begin
	case(unsigned_status)
		2'd0://waiting inst
			unsigned_next_status = (es_div_wu | es_mod_wu) & es_valid ? 2'd1 : 2'd0;
		2'd1://waiting input
			unsigned_next_status = unsigned_s_axis_divisor_tready & unsigned_s_axis_dividend_tready ? 2'd2 : 2'd1;
		2'd2://waiting output
			unsigned_next_status = unsigned_output_valid ? 2'd0 : 2'd2;
		default:
			unsigned_next_status = 2'd3;
	endcase
end
	
assign unsigned_input_valid = unsigned_status == 2'd1;

mydiv_un u_mydiv_un(
	.aclk (clk),
	.s_axis_divisor_tvalid (unsigned_input_valid),
	.s_axis_divisor_tready (unsigned_s_axis_divisor_tready),
	.s_axis_divisor_tdata (es_alu_src2),
	.s_axis_dividend_tvalid (unsigned_input_valid),
	.s_axis_dividend_tready (unsigned_s_axis_dividend_tready),
	.s_axis_dividend_tdata (es_alu_src1),
	.m_axis_dout_tvalid (unsigned_output_valid),
	.m_axis_dout_tdata (unsigned_output_data)
	);

assign es_alu_result = es_mul_w ? unsigned_prod[31:0] :
                      es_mulh_w ? signed_prod[63:32] :
                      es_mulh_wu ? unsigned_prod[63:32] :
                      es_div_w ? signed_output_data[63:32] :
                      es_mod_w ? signed_output_data[31:0] :
                      es_div_wu ? unsigned_output_data[63:32] :
                      es_mod_wu ? unsigned_output_data[31:0] :
                      alu_result;

assign data_sram_addr  = alu_result;
assign data_sram_en = (es_res_from_mem || es_mem_we) && es_valid;
assign data_sram_wdata = es_rkd_value;
assign data_sram_we = {4{es_mem_we}};

endmodule
